using System.Text;
using MagicOnion.Client.SourceGenerator.CodeAnalysis;

namespace MagicOnion.Client.SourceGenerator.CodeGen.MessagePack;

internal class MessagePackFormatterResolverGenerator() : ISerializerFormatterGenerator
{
    public (string HintNameSuffix, string Source) Build(GenerationContext generationContext, SerializationFormatterCodeGenContext ctx)
    {
        using var pooledStringBuilder = generationContext.GetPooledStringBuilder();
        var writer = pooledStringBuilder.Instance;

        EmitPreamble(generationContext, ctx, writer);
        EmitBody(generationContext, ctx, writer);
        EmitPostscript(generationContext, ctx, writer);

        return (".MessagePack", writer.ToString());
    }

    void EmitPreamble(GenerationContext generationContext, SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
        writer.AppendLine("""
            // <auto-generated />
            #pragma warning disable
            
            """);
    }

    void EmitBody(GenerationContext generationContext, SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
        if (!string.IsNullOrEmpty(generationContext.Namespace))
        {
            writer.AppendLineWithFormat($$"""
            namespace {{generationContext.Namespace}}
            {
            """);
        }
        writer.AppendLineWithFormat($$"""
                using global::System;
                using global::MessagePack;

                partial class {{generationContext.InitializerPartialTypeName}}
                {
                    /// <summary>
                    /// Gets the generated MessagePack formatter resolver.
                    /// </summary>
                    public static global::MessagePack.IFormatterResolver Resolver => MessagePackGeneratedResolver.Instance;
            """);

        EmitResolver(ctx, writer);
        EmitGetFormatterHelper(ctx, writer);
        EmitTypeHints(ctx, writer);

        writer.AppendLineWithFormat($$"""
                }
            """);

        if (!string.IsNullOrEmpty(generationContext.Namespace))
        {
            writer.AppendLineWithFormat($$"""
            }
            """);
        }
    }

    void EmitResolver(SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
        writer.AppendLineWithFormat($$"""
                    class MessagePackGeneratedResolver : global::MessagePack.IFormatterResolver
                    {
                        public static readonly global::MessagePack.IFormatterResolver Instance = new MessagePackGeneratedResolver();

                        MessagePackGeneratedResolver() {}

                        public global::MessagePack.Formatters.IMessagePackFormatter<T> GetFormatter<T>()
                            => FormatterCache<T>.formatter;

                        static class FormatterCache<T>
                        {
                            public static readonly global::MessagePack.Formatters.IMessagePackFormatter<T> formatter;

                            static FormatterCache()
                            {
                                var f = MessagePackGeneratedGetFormatterHelper.GetFormatter(typeof(T));
                                if (f != null)
                                {
                                    formatter = (global::MessagePack.Formatters.IMessagePackFormatter<T>)f;
                                }
                            }
                        }
                    }
            """);
    }

    void EmitGetFormatterHelper(SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
        var formatterRegistrations = ctx.FormatterRegistrations;

        writer.AppendLineWithFormat($$"""
                    static class MessagePackGeneratedGetFormatterHelper
                    {
                        static readonly global::System.Collections.Generic.Dictionary<global::System.Type, int> lookup;

                        static MessagePackGeneratedGetFormatterHelper()
                        {
                            lookup = new global::System.Collections.Generic.Dictionary<global::System.Type, int>({{formatterRegistrations.Count}})
                            {
            """);
        foreach (var (resolverInfo, index) in formatterRegistrations.Select((x, i) => (x, i)))
        {
            writer.AppendLineWithFormat($$"""
                                {typeof({{resolverInfo.FullName}}), {{index}}},
            """);
        }
        writer.AppendLineWithFormat($$"""
                            };
                        }
                        internal static object GetFormatter(global::System.Type t)
                        {
                            int key;
                            if (!lookup.TryGetValue(t, out key))
                            {
                                return null;
                            }
                        
                            switch (key)
                            {
            """);
        foreach (var (resolverInfo, index) in formatterRegistrations.Select((x, i) => (x, i)))
        {
            if (resolverInfo is EnumSerializationInfo)
            {
                // Use EnumFormatter generated by MagicOnion.Client.SourceGenerator
                writer.AppendLineWithFormat($$"""
                                case {{index}}: return new MessagePackEnumFormatters.{{resolverInfo.FormatterName}}{{resolverInfo.FormatterConstructorArgs}};
            """);
            }
            else
            {
                // Use formatter generated by MessagePack generator.
                writer.AppendLineWithFormat($$"""
                                case {{index}}: return new {{(resolverInfo.FormatterName.StartsWith("global::") || string.IsNullOrWhiteSpace(ctx.FormatterNamespace) ? "" : ctx.FormatterNamespace + ".") + resolverInfo.FormatterName}}{{resolverInfo.FormatterConstructorArgs}};
            """);
            }
        }
        writer.AppendLineWithFormat($$"""
                                default: return null;
                            }
                        }
                    }
            """);
    }

    void EmitTypeHints(SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
        writer.AppendLineWithFormat($$"""
                    /// <summary>Type hints for Ahead-of-Time compilation.</summary>
                    [Preserve]
                    static class TypeHints
                    {
                        [Preserve]
                        internal static void Register()
                        {
            """);
        foreach (var typeHint in ctx.TypeHints)
        {
            writer.AppendLineWithFormat($$"""
                            _ = MessagePackGeneratedResolver.Instance.GetFormatter<{{typeHint.FullName}}>();
            """);
        }
        writer.AppendLineWithFormat($$"""
                        }
                    }
            """);
    }

    void EmitPostscript(GenerationContext generationContext, SerializationFormatterCodeGenContext ctx, StringBuilder writer)
    {
    }
}
